#include "BP.h"

BP::BP(shared_ptr<CMNN> network, double impulseParam, function<double(int)> backfireFunction, double EPS) :
	m_Network(network),
	m_ImpulseParam(impulseParam),
	m_BackfireFunction(backfireFunction),
	m_EPS(EPS)
{
	this->m_ErrorN = 0;

	this->m_pMeanSquareError = make_shared<Vector>(this->m_Network->GetLayer(this->m_Network->GetLayerCount() - 1).size());
}

bool BP::CheckError(Vector error)
{
	auto dimCount = error.GetDimesion();
	
	if (this->m_EPS > 0.0)
	{
		Vector oldSquareError(*this->m_pMeanSquareError);

		++this->m_ErrorN;

		for (int i = 0; i < dimCount; i++)
		{
			(*this->m_pMeanSquareError)[i] = ((this->m_ErrorN - 1) * (this->m_pMeanSquareError->GetVectorElement(i) + error[i] * error[i])) / (1.0 * this->m_ErrorN);
		}

		for (int i = 0; i < dimCount; i++)
		{
			double MSES = ::fabs(this->m_pMeanSquareError->GetVectorElement(i) - oldSquareError[i]);

			// printf("MSES<%d> = %f\n", i, MSES);

			if (MSES > this->m_EPS)
			{
				return false;
			}
		}
	}
	else
	{
		double Eps = -1.0 * this->m_EPS;

		for (int i = 0; i < dimCount; i++)
		{
			double ErrVal = ::fabs((*this->m_pMeanSquareError)[i] - error[i]);

			// printf("ErrVal = %f  EPS=%f\n", ErrVal, Eps);

			if (ErrVal > Eps)
			{
				return false;
			}

			(*this->m_pMeanSquareError)[i] = error[i];
		}
	}
	
	return true;
}

void BP::Training(Vector Input, Vector ExceptOutput)
{
	int N = 1;
	while (true)
	{
		N++;

		auto vMnnOutput = this->m_Network->Execute(Input);

		auto vError = ExceptOutput - vMnnOutput;
		
		if (this->CheckError(vError))
		{
			break;
		}

		auto maxLayerIndex = this->m_Network->GetLayerCount() - 1;

		int neuronIndex = 0;
		///////////////////////////////
		auto curLayer = this->m_Network->GetLayer(maxLayerIndex);
		auto vLocalGradient = vError * this->m_Network->GetLayerPartLocalGradient(maxLayerIndex);

		neuronIndex = 0;

		for (auto it_neuron = curLayer.begin(); it_neuron != curLayer.end(); ++it_neuron, ++neuronIndex)
		{
			auto neuron = *it_neuron;

			auto orgW = neuron->GetWeightVector();
			auto impulesItem = neuron->GetLastDeltaWeightVector() * this->m_ImpulseParam;
			auto deltaW = neuron->GetNeuronInput() * this->m_BackfireFunction(N) * vLocalGradient[neuronIndex];

			auto newWeight = orgW + impulesItem + deltaW;

			neuron->SetLastDeltaWeightVector(impulesItem + deltaW);
			neuron->SetWeightVector(newWeight);
		}

		////////////////////////////////
		for (int layerIndex = maxLayerIndex - 1; layerIndex >= 0; --layerIndex)
		{
			auto lastLayer = this->m_Network->GetLayer(layerIndex + 1);
			auto vLastLocalGradient = vLocalGradient;

			curLayer = this->m_Network->GetLayer(layerIndex);

			vLocalGradient.ResetDimesion(curLayer.size());

			neuronIndex = 0;

			for (auto it_neuron = curLayer.begin(); it_neuron != curLayer.end(); ++it_neuron, ++neuronIndex)
			{
				auto neuron = *it_neuron;

				int lastLayerNeuronIndex = 0;
				vLocalGradient[neuronIndex] = 0;

				for (auto it_last_neuron = lastLayer.begin(); it_last_neuron != lastLayer.end(); ++it_last_neuron, ++lastLayerNeuronIndex)
				{
					auto lastLayerNeuron = *it_last_neuron;

					auto W = lastLayerNeuron->GetLastWeightVector();
					
					//for (int i = 0; i < W.GetDimesion(); i++)
					//{
					// 	vLocalGradient[neuronIndex] += (vLastLocalGradient[lastLayerNeuronIndex] * W[i]);
					//}

					vLocalGradient[neuronIndex] += (vLastLocalGradient[lastLayerNeuronIndex] * W[neuronIndex]);
				}
				
				vLocalGradient[neuronIndex] *= neuron->GetTransferFunctionObject()->D_Do(neuron->GetInducedLocal());

				auto orgW = neuron->GetWeightVector();
				auto impulseItem = neuron->GetLastDeltaWeightVector() * this->m_ImpulseParam;
				auto deltaW = neuron->GetNeuronInput() * this->m_BackfireFunction(N) * vLocalGradient[neuronIndex];

				auto newWeight = orgW + impulseItem + deltaW;
				
				neuron->SetLastDeltaWeightVector(impulseItem + deltaW);
				neuron->SetWeightVector(newWeight);
			}
		}
	}
}